Passed
Branch wavefile-rw (a1d6f9)
by Rafael S.
02:39
created

WaveFileCreator.createPCMHeader_   A

Complexity

Conditions 1

Size

Total Lines 20
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 20
rs 9.5
c 0
b 0
f 0
cc 1
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileCreator class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import WaveFileParser from './wavefile-parser';
31
import interleave from './interleave';
32
import dwChannelMask from './dw-channel-mask';
33
import {packArrayTo} from 'byte-data';
34
35
/**
36
 * A class to read, write and create wav files.
37
 */
38
export default class WaveFileCreator extends WaveFileParser {
39
40
  /**
41
   * Set up the WaveFileCreator object based on the arguments passed.
42
   * @param {number} numChannels The number of channels
43
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
44
   * @param {number} sampleRate The sample rate.
45
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
46
   * @param {string} bitDepthCode The audio bit depth code.
47
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
48
   *    or any value between '8' and '32' (like '12').
49
   * @param {!Array<number>|!Array<!Array<number>>|!TypedArray} samples
50
   *    The samples. Must be in the correct range according to the bit depth.
51
   * @param {?Object} options Optional. Used to force the container
52
   *    as RIFX with {'container': 'RIFX'}
53
   * @throws {Error} If any argument does not meet the criteria.
54
   */
55
  create(numChannels, sampleRate, bitDepthCode, samples, options={}) {
56
    if (!options.container) {
57
      options.container = 'RIFF';
58
    }
59
    this.container = options.container;
60
    this.bitDepth = bitDepthCode;
61
    samples = interleave(samples);
62
    this.updateDataType();
63
    /** @type {number} */
64
    let numBytes = this.dataType.bits / 8;
65
    this.data.samples = new Uint8Array(samples.length * numBytes);
66
    packArrayTo(samples, this.dataType, this.data.samples);
67
    this.clearHeader();
68
    this.makeWavHeader_(
69
      bitDepthCode, numChannels, sampleRate,
70
      numBytes, this.data.samples.length, options);
71
    this.data.chunkId = 'data';
72
    this.data.chunkSize = this.data.samples.length;
73
    this.validateWavHeader();
74
  }
75
76
  /**
77
   * Define the header of a wav file.
78
   * @param {string} bitDepthCode The audio bit depth
79
   * @param {number} numChannels The number of channels
80
   * @param {number} sampleRate The sample rate.
81
   * @param {number} numBytes The number of bytes each sample use.
82
   * @param {number} samplesLength The length of the samples in bytes.
83
   * @param {!Object} options The extra options, like container defintion.
84
   * @private
85
   */
86
  makeWavHeader_(
87
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
88
    if (bitDepthCode == '4') {
89
      this.createADPCMHeader_(
90
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
91
92
    } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
93
      this.createALawMulawHeader_(
94
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
95
96
    } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
97
        numChannels > 2) {
98
      this.createExtensibleHeader_(
99
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
100
101
    } else {
102
      this.createPCMHeader_(
103
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
104
    }
105
  }
106
107
  /**
108
   * Create the header of a linear PCM wave file.
109
   * @param {string} bitDepthCode The audio bit depth
110
   * @param {number} numChannels The number of channels
111
   * @param {number} sampleRate The sample rate.
112
   * @param {number} numBytes The number of bytes each sample use.
113
   * @param {number} samplesLength The length of the samples in bytes.
114
   * @param {!Object} options The extra options, like container defintion.
115
   * @private
116
   */
117
  createPCMHeader_(
118
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
119
    this.container = options.container;
120
    this.chunkSize = 36 + samplesLength;
121
    this.format = 'WAVE';
122
    this.bitDepth = bitDepthCode;
123
    this.fmt = {
124
      chunkId: 'fmt ',
125
      chunkSize: 16,
126
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
127
      numChannels: numChannels,
128
      sampleRate: sampleRate,
129
      byteRate: (numChannels * numBytes) * sampleRate,
130
      blockAlign: numChannels * numBytes,
131
      bitsPerSample: parseInt(bitDepthCode, 10),
132
      cbSize: 0,
133
      validBitsPerSample: 0,
134
      dwChannelMask: 0,
135
      subformat: []
136
    };
137
  }
138
139
  /**
140
   * Create the header of a ADPCM wave file.
141
   * @param {string} bitDepthCode The audio bit depth
142
   * @param {number} numChannels The number of channels
143
   * @param {number} sampleRate The sample rate.
144
   * @param {number} numBytes The number of bytes each sample use.
145
   * @param {number} samplesLength The length of the samples in bytes.
146
   * @param {!Object} options The extra options, like container defintion.
147
   * @private
148
   */
149
  createADPCMHeader_(
150
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
151
    this.createPCMHeader_(
152
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
153
    this.chunkSize = 40 + samplesLength;
154
    this.fmt.chunkSize = 20;
155
    this.fmt.byteRate = 4055;
156
    this.fmt.blockAlign = 256;
157
    this.fmt.bitsPerSample = 4;
158
    this.fmt.cbSize = 2;
159
    this.fmt.validBitsPerSample = 505;
160
    this.fact = {
161
      chunkId: 'fact',
162
      chunkSize: 4,
163
      dwSampleLength: samplesLength * 2
164
    };
165
  }
166
167
  /**
168
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
169
   * @param {string} bitDepthCode The audio bit depth
170
   * @param {number} numChannels The number of channels
171
   * @param {number} sampleRate The sample rate.
172
   * @param {number} numBytes The number of bytes each sample use.
173
   * @param {number} samplesLength The length of the samples in bytes.
174
   * @param {!Object} options The extra options, like container defintion.
175
   * @private
176
   */
177
  createExtensibleHeader_(
178
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
179
    this.createPCMHeader_(
180
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
181
    this.chunkSize = 36 + 24 + samplesLength;
182
    this.fmt.chunkSize = 40;
183
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
184
    this.fmt.cbSize = 22;
185
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
186
    this.fmt.dwChannelMask = dwChannelMask(numChannels);
187
    // subformat 128-bit GUID as 4 32-bit values
188
    // only supports uncompressed integer PCM samples
189
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
190
  }
191
192
  /**
193
   * Create the header of mu-Law and A-Law wave files.
194
   * @param {string} bitDepthCode The audio bit depth
195
   * @param {number} numChannels The number of channels
196
   * @param {number} sampleRate The sample rate.
197
   * @param {number} numBytes The number of bytes each sample use.
198
   * @param {number} samplesLength The length of the samples in bytes.
199
   * @param {!Object} options The extra options, like container defintion.
200
   * @private
201
   */
202
  createALawMulawHeader_(
203
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
204
    this.createPCMHeader_(
205
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
206
    this.chunkSize = 40 + samplesLength;
207
    this.fmt.chunkSize = 20;
208
    this.fmt.cbSize = 2;
209
    this.fmt.validBitsPerSample = 8;
210
    this.fact = {
211
      chunkId: 'fact',
212
      chunkSize: 4,
213
      dwSampleLength: samplesLength
214
    };
215
  }
216
}
217